/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package gestor.base.de.datos;

import java.io.*;
import java.util.*;

/**
 *
 * @author Ana Silvia Gonzáles
 * @author Diego Enríquez
 */

public class Table implements Serializable {
    
    
    private String name;
    private Map<String,Column> columns;
    private ArrayList<String> posiciones;
    private Map<String,Constraint> constraints;
    private int foreign;
    private boolean primary;
    private int cantPrimary;
    
    public Table(String name)
    {
        
        this.name=name.toUpperCase();
        this.columns=new HashMap<>();
        this.posiciones=new ArrayList<>();
        this.constraints=new HashMap<>();
        this.foreign=0;
        this.primary=false;
        this.cantPrimary=0;
        
    }

    /**
     * Convierte en collection todos los constraints
     * @return collection de constraints
     */
    public Collection<Constraint> toListConstraint()
    {
        
        return this.constraints.values();
        
    }
    
    public void setConstraints(Map<String, Constraint> constraints) {
        this.constraints = constraints;
    }

    public Map<String, Constraint> getConstraints() {
        return constraints;
    }

    public void setPosiciones(ArrayList<String> posiciones) {
        this.posiciones = posiciones;
    }

    public ArrayList<String> getPosiciones() {
        return posiciones;
    }

    public void setName(String name) {
        this.name = name.toUpperCase();
    }

    public void setColumns(Map<String, Column> columns) {
        this.columns = columns;
    }

    public String getName() {
        return name;
    }

    public Map<String, Column> getColumns() {
        return columns;
    }

    /**
     * Ingresa una columna a la tabla con valores
     * @param name
     * @param columna 
     */
    public void setColumn(String name,Column columna)
    {
        
        Column col=new Column(columna.getTipo());
        col.setRegistros((ArrayList<String>) columna.getRegistros().clone());
        
        if(!columns.containsKey(name.toUpperCase()))
        {
            columns.put(name.toUpperCase(), col);
            posiciones.add(name.toUpperCase());
        }
        
    }
    /**
     * Agrega una columna nueva a la tabla
     * @param name
     * @param tipo
     * @param key
     * @return true si no hay una columna con ese nombre, false si lo hay
     */
    public boolean addColumn(String name, String tipo)
    {
        boolean resp;
        
        if(columns.containsKey(name.toUpperCase()))
        {
            resp=false;
            gui.GUI.error("Ya existe la columna "+name);
        }
        else
        {
            Column nueva=new Column(tipo);
            for(int i=0;i<this.getCantRegistros();i++)
            {
                nueva.getRegistros().add("'NULL'");
            }
            columns.put(name.toUpperCase(), nueva);
            posiciones.add(name.toUpperCase());
            resp=true;
            
            gui.GUI.error("\n LA COLUMNA "+name.toUpperCase()+" HA SIDO AGREGADA");
        }
        
        return resp;
    }
    
    /**
     * Elimina una columna de la tabla
     * @param name
     * @return true si la columna se eliminó, false si no 
     */
    public boolean dropColumn(String name)
    {
        boolean resp=true;
        
        
        if(columns.containsKey(name.toUpperCase()))
        {
            
            if(!checkPrimary(name))
            {
                if(columns.get(name).getForeign()==0)
                {
                    columns.remove(name.toUpperCase());
                    posiciones.remove(name.toUpperCase());
                }
                else
                {
                    resp=false;
                }
            }
            else {
                resp=false;
            }
        }
        else {
            resp=false;
            gui.GUI.error("No existe la columna "+name.toUpperCase());
        }
        
        return resp;   
            
    }
    
    /**
     * Inserta una nueva fila en la tabla, antes revisa si la cantidad de datos
     * es correcta y si no existe una fila con los mismos keys
     * @param row
     * @return true si la pudo insertar, false si no
     */
    public boolean insert(ArrayList<String> cols,ArrayList<String> row)
    {
        
        boolean resp=true;
        
        if(cols==null)
        {
            if(row.size()>posiciones.size())
            {
                resp=false;
                gui.GUI.error("La tabla no tiene tantas columnas");
            }
            else
            {
                while(row.size()<posiciones.size())
                {
                    row.add("'NULL'");
                }
                
                if(checkRow(row))
                {
                    if(checkConstraints(row))
                    {
                        for(int i=0;i<row.size();i++)
                        {
                            columns.get(posiciones.get(i)).addField(row.get(i));
                        }
                    }
                    else
                    {
                        resp=false;
                    }
                }
                else
                {
                    resp=false;
                }

            }
        }
        else
        {
            int tam=0;
            for(int i=0;i<cols.size();i++)
            {
                if(checkExists(cols.get(i)))
                {
                    columns.get(cols.get(i)).addField(row.get(i).toUpperCase());
                    tam=columns.get(cols.get(i)).getRegistros().size();
                }
                else
                {
                    resp=false;
                    gui.GUI.error("No existe la columna "+cols.get(i));
                }
            }
            for(int i=0;i<posiciones.size();i++)
            {
                if(columns.get(posiciones.get(i)).getRegistros().size()<tam)
                {
                    columns.get(posiciones.get(i)).addField("NULL");
                }
            }
        }
        
        
        return resp;
        
    }
    
    /**
     * Revisa que el registro cumpla con las condiciones de los checks
     * @param row
     * @return true si cumple, false si no
     */
    public boolean checkConstraints(ArrayList<String> row)
    {
        
        boolean resp=true;
        
        for(String key: constraints.keySet())
        {
            if(!constraints.get(key).check(this, row))
            {
                resp=false;
                gui.GUI.error("El registro no cumple con los checks de las constraints");
            }
        }
        
        return resp;
        
    }
    
    /**
     * Revisa que no hayan primary keys repetidas
     * @param row
     * @return true si cumple, false si no
     */
    public boolean checkRow(ArrayList<String> row) {
        boolean resp = true;

        
        int cantKeys = 0;
        int noVal = 0;
        
        for (int j = 0; j < row.size(); j++) {            

            for (String key : constraints.keySet()) {
                if (constraints.get(key).getKeys().size() > 0) {
                    ArrayList<Key> keys = constraints.get(key).getKeys();
                    for (int k = 0; k < keys.size(); k++) {
                        
                        if ((keys.get(k).getTipo()==1)&&(getColNum(keys.get(k).getId()) == j)) {
                            cantKeys++;
                            if ((row.get(j).equals("'NULL'")) || (columns.get(posiciones.get(j)).findField(row.get(j)).size() > 0)) {
                                noVal++;
                            }
                        }
                    }
                }
            }
            
        }

        if((cantKeys>0)&&(noVal==cantKeys))    
        {
            resp=false;
            gui.GUI.error("Ya existe un registro con esa primary key");
        }
        
        return resp;
    }
    
    /**
     * Revisa si la tabla contiene una columna con el nombre en name
     * @param name
     * @return true si la contiene, false si no
     */
    public boolean checkExists(String name)
    {
        
        boolean resp;
        
        resp=columns.containsKey(name.toUpperCase());
        
        return resp;
        
    }
    
    /**
     * Busca el numero que le pertenece a una columna
     * @param name
     * @return -1 si no existe la columna o el número de posición en la que se encuentra
     */
    public int getColNum(String name)
    {
        
        int resp=-1;
        
        if(columns.containsKey(name.toUpperCase()))
        {
            int i=0;
            while((resp==-1)&&(i<posiciones.size()))
            {
                if(posiciones.get(i).equals(name.toUpperCase())) {
                    resp=i;
                }
                i++;
            }
        }
        
        return resp;
        
    }
    
    /**
     * Busca la columna correspondiente a la posición solicitada
     * @param pos
     * @return devuelve el objeto correspondiente a la posición
     */
    public Column getCol(int pos)
    {
        
        Column resp=null;
        
        if((pos>=0)&&(pos<posiciones.size())) {
            resp=columns.get(posiciones.get(pos));
        }
        
        
        return resp;
        
    }
    
    /**
     * Muestra la tabla
     * @return String con la tabla
     */
    public String showTable()
    {
        
        String resp="";
        String encabezado="";
        int cantReg;
        ArrayList<Integer> tam=new ArrayList<>();
        
        if(posiciones.size()>0)
        {
            String val;
            cantReg=columns.get(posiciones.get(0)).getRegistros().size();
            for(int i=0;i<posiciones.size();i++)
            {
                tam.add(posiciones.get(i).length()+2);
            
                for(int j=0;j<cantReg;j++)
                {
                    val= columns.get(posiciones.get(i)).getRegistros().get(j);
                    if((val.length()+2)>tam.get(i)) {
                        tam.set(i,2+ val.length());
                    }
                }
            }
            
            for(int j=0;j<cantReg;j++)
            {
                for(int i=0;i<posiciones.size();i++)
                {
                    val= " "+columns.get(posiciones.get(i)).getRegistros().get(j)+" ";
                    while(val.length()<=tam.get(i)) {
                        val+=" ";
                    }
                    
                    resp+=val+"|";
                }
                resp+="\n";
            }
            
            encabezado="";
            for(int i=0;i<posiciones.size();i++)
            {
                val=" "+posiciones.get(i)+" ";
                while(val.length()<=tam.get(i)) {
                    if((val.length()%2)==0) {
                        val+=" ";
                    }
                    else {
                        val=" "+val;
                    }
                }
                encabezado+=val+"|";
            }
            encabezado=encabezado+"\n";
            int tamE=encabezado.length();
            for(int i=0;i<tamE;i++) {
                encabezado=encabezado+"-";
            }
            encabezado+="\n";
        }
        
        resp=encabezado+resp;
        
        return resp;
        
    }
   
    /**
     * Busca el tipo de la columna indicada
     * @param columna
     * @return el tipo si la columna existe, si no existe un string vacío
     */
    public String getTipoCol(String columna)
    {
        
        String resp="";
        
        if(columns.containsKey(columna.toUpperCase())) {
            resp=columns.get(columna.toUpperCase()).getTipo();
        }
        
        return resp;
        
    }
    
    
    /**
     * Obtiene la cantidad de registros que posee la tabla
     * @return la cantidad de registros de la tabla
     */
    public int getCantRegistros()
    {
        int resp=0;
        
        if(posiciones.size()>0) {
            resp=columns.get(posiciones.get(0)).getRegistros().size();
        }
        
        return resp;
    }
    
    /**
     * Obtiene la cantidad de columnas que posee la tabla
     * @return el tamaño del atributo posiciones
     */
    public int getCantCol()
    {
        
        return posiciones.size();
        
    }
    
    /**
     * Obtiene el nombre de la columna en la posición indicada
     * @param pos
     * @return String vacío si la posición no existe, de lo contrario el nombre
     */
    public String getColName(int pos)
    {
        
        String resp="";
        
        if((pos>=0)&&(pos<posiciones.size())) {
            resp=posiciones.get(pos);
        }
        
        
        return resp;
        
    }
    
    /**
     * Obtiene el tipo de la columna en la posición indicada
     * @param pos
     * @return String vacío si no existe la posición, de lo contrario el tipo
     */
    public String getTipoCol(int pos)
    {
        
        String resp="";
        
        if((pos>=0)&&(pos<posiciones.size())) {
            resp=columns.get(posiciones.get(pos)).getTipo();
        }
        
        
        return resp;
        
    }
    
    /**
     * Obtiene la columna indicada
     * @param name
     * @return null si no existe la columna, de lo contrario la columna
     */
    public Column getCol(String name)
    {
        Column resp=null;
        
        if(columns.containsKey(name.toUpperCase()))
        {
            resp=columns.get(name.toUpperCase());
        }
        
        return resp;
    }
    
    /**
     * Elimina el registro en la posicion indicada
     * @param pos 
     */
    public void delRegistro(int pos)
    {
        for(int i=0;i<posiciones.size();i++)
        {
            columns.get(posiciones.get(i)).removeField(pos);
        }
    }
    
    /**
     * Elimina el registro en la posicion indicada sin revisar constraints
     * @param pos 
     */
    public void delRegistroNoCheck(int pos)
    {
        for(int i=0;i<posiciones.size();i++)
        {
            columns.get(posiciones.get(i)).removeField(pos);
        }
    }
    
    /**
     * Obtiene el subarray de los registros de la tabla
     * @param from
     * @param to
     * @return tabla con los registros en el rango indicado
     */
    public Table getRegistros(int from, int to)
    {
        Table resp=new Table("sub");
        
        for(int i=0;i<posiciones.size();i++)
        {
            Column columna=(columns.get(posiciones.get(i)).getRegistros(from, to));
            resp.setColumn(posiciones.get(i), columna);
        }
        
        return resp;
    }
    
    /**
     * Agrega un registro a la tabla sin revisar las constraints
     * @param registro 
     */
    public void addNoCheck(ArrayList<String> registro)
    {
        
        for(int i=0;i<registro.size();i++)
        {
            columns.get(posiciones.get(i)).addField(registro.get(i));
        }
        
    }
    
    /**
     * Obtiene el registro en la posicion indicada
     * @param pos
     * @return arraylist de los registros
     */
    public ArrayList<String> getRegistro(int pos)
    {
        ArrayList<String> resp=new ArrayList<>();
        
        for(int i=0;i<posiciones.size();i++)
        {
            resp.add(columns.get(posiciones.get(i)).getRegistros().get(pos));
        }
        
        return resp;
    }
    
    /**
     * Agrega una constraint check a la tabla
     * @param id
     * @param condicion
     * @return true si la agregó , de lo contrario false
     */
    public boolean addConstraint(String id, Stack<String>[] condicion)
    {
        
        boolean resp=true;
        
        if(constraints.containsKey(id.toUpperCase()))
        {
            resp=false;
            gui.GUI.error("Ya existe la constraint "+id);
        }
        else
        {
            Constraint con=new Constraint(id.toUpperCase());
            con.addCheck(new Check(condicion));
            constraints.put(id.toUpperCase(), con);
        }
        
        return resp;
    }
    
    /**
     * Agrega una constraint foreign key la tabla
     * @param id
     * @param col
     * @param tablaRef
     * @param colRef
     * @return true si la agregó, de lo contrario false
     */
    public boolean addConstraint(String id, String col, String tablaRef, String colRef)
    {
        boolean resp=true;
        
        if(constraints.containsKey(id.toUpperCase()))
        {
            resp=false;
            gui.GUI.error("Ya existe esa constraint");
        }
        else
        {
            //if(this.primary)
            //{
            Constraint con=new Constraint(id.toUpperCase());
            con.addKey(new Key(col.toUpperCase(),2,tablaRef.toUpperCase(),colRef.toUpperCase()));
            constraints.put(id.toUpperCase(), con);
           /* }
            else
            {
                gui.GUI.error("LA TABLA SOLO PUEDE TENER UN PRIMARY KEY");
            }*/
            
        }
        
        return resp;
        
    }
    
    /**
     * Agrega una constraint primary key
     * @param id
     * @param col
     * @return 
     */
    public boolean addConstraint(String id, String col)
    {
        
        boolean resp=true;
        
        if(constraints.containsKey(id.toUpperCase()))
        {
            resp=false;
            gui.GUI.error("Ya existe un constraint con el id "+id);
        }
        else
        {
            
            if(!this.primary)
            {
            Constraint con=new Constraint(id.toUpperCase());
            con.addKey(new Key(col,1,"",""));
            constraints.put(id.toUpperCase(), con);
            cantPrimary++;
            }
            else
            {
                gui.GUI.error("LA TABLA SOLO PUEDE TENER UN PRIMARY KEY");
            }
            
        }
        
        return resp;
        
    }
    /**
     * Agrega una referencia de llave foranea
     * @param col 
     */
    public void addForeign(String col)
    {
        this.foreign++;
        columns.get(col.toUpperCase()).addForeign();
        
    }
    
    /**
     * Elimina una referencia de llave foranea
     * @param col 
     */
    public void lessForeign(String col)
    {
        this.foreign--;
        columns.get(col.toUpperCase()).lessForeign();
    }

    public void setForeign(int foreign) {
        this.foreign = foreign;
    }

    public int getForeign() {
        return foreign;
    }
    
    /**
     * Reevisa si la columna es un primary key
     * @param col
     * @return true si lo es, false si no
     */
    public boolean checkPrimary(String col)
    {
        
        boolean resp=false;
        
        for(String key:constraints.keySet())
        {
            if(constraints.get(key).getKeys().size()>0)
            {
                ArrayList<Key> keys=constraints.get(key).getKeys();
                int i=0;
                while((!resp)&&(i<keys.size()))
                {
                    if(keys.get(i).getId().equals(col.toUpperCase()))
                    {
                        resp=true;
                    }
                }
            }
        }
        
        return resp;
                
    }
    
    /**
     * Elimmina una constraint
     * @param id 
     */
    public void dropConstraint(String id)
    {
        if(constraints.containsKey(id))
        {
            if(constraints.get(id).getKeys().size()>0)
            {
                ArrayList<Key> keys=constraints.get(id).getKeys();
                for(int i=0;i<keys.size();i++)
                {
                    if(keys.get(i).getTipo()==1)
                    {
                        cantPrimary--;
                    }
                }
            }
            constraints.remove(id);
            if(cantPrimary==0)
            {
                primary=false;
            }
        }
        else
        {
            gui.GUI.error("No existe la constraint con el id "+id);
        }
    }

    public void setPrimary(boolean primary) {
        this.primary = primary;
    }

    public boolean isPrimary() {
        return primary;
    }
    
}
